﻿using System;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Yahoo.Yui.Compressor;

namespace Chatu.YUICompress.MsBuild
{
    public class ChatuCompressorTask : Task
    {
        #region Fields
        
        private CssCompressionType _cssCompressionType = CssCompressionType.StockYuiCompressor;
        private LoggingType _loggingType = LoggingType.Minimal;
        
        //Optional Elements default values
        private bool _deleteCssFiles = true;
        private bool _deleteJavaScriptFiles = true;
        private bool _obfuscateJavaScript = false;
        private bool _preserveAllSemicolons = true;
        private bool _disableOptimizations = true;
        private string _newFileAppend = "";
        private int _lineBreak = 2500;

        #endregion

        #region Properties

        public ITaskItem[] CssFiles { get; set; }
        public ITaskItem[] JavaScriptFiles { get; set; }

        public bool DeleteCssFiles 
        {
            get { return _deleteCssFiles; }
            set { _deleteCssFiles = value; }
        }

        public bool DeleteJavaScriptFiles 
        {
            get { return _deleteJavaScriptFiles; }
            set { _deleteJavaScriptFiles = value; }
        }

        public bool ObfuscateJavaScript
        {
            get { return _obfuscateJavaScript; }
            set { _obfuscateJavaScript = value; }
        }

        public bool PreserveAllSemicolons 
        {
            get { return _preserveAllSemicolons; }
            set { _preserveAllSemicolons = value; }
        }

        public bool DisableOptimizations 
        {
            get { return _disableOptimizations; }
            set { _disableOptimizations = value; }
        }

        public string NewFileAppend 
        {
            get { return _newFileAppend; }
            set { _newFileAppend = value; }
        }

        public int LineBreak 
        {
            get { return _lineBreak; }
            set { _lineBreak = value; } 
        }

        #endregion

        #region Methods

        private void LogMessage(string message)
        {
            LogMessage(message, false);
        }

        private void LogMessage(string message, bool isIndented)
        {
            Log.LogMessage("{0}{1}", isIndented ? "    " : string.Empty, message);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void CompressFiles(ActionType actionType, ITaskItem[] fileList)
        {
            string originalContent;
            int originalContentLength = 0;
            string compressedContent = null;
            int compressedContentLength = 0;

            LogMessage(string.Format("{0}# Found one or more {1} file arguments. Now parsing ...", System.Environment.NewLine, actionType.ToString()));

            if (actionType == ActionType.JavaScript)
            {
                LogMessage(string.Format("** Obfuscate Javascript: {0}",
                    _obfuscateJavaScript.ToString()), true);
                LogMessage(string.Format("** Preserve semi colons: {0}",
                    _preserveAllSemicolons.ToString()), true);
                LogMessage(string.Format("** Disable optimizations: {0}",
                    _disableOptimizations.ToString()), true);
            }
            else if (actionType == ActionType.Css)
            {
                LogMessage(string.Format("** Css Compression Type: {0}", _cssCompressionType.ToString()), true);
            }

            LogMessage(string.Format("# {0} {1} file{2} requested.",
                fileList.Length,
                actionType.ToString(),
                fileList.Length.ToPluralString()));

            foreach (ITaskItem file in fileList)
            {
                LogMessage("=> " + file.ItemSpec, true);
            }

            //Now compress each file.
            foreach (ITaskItem file in fileList)
            {
                //Load up the file.
                try
                {
                    originalContent = File.ReadAllText(file.ItemSpec);
                    originalContentLength = originalContent.Length;

                    if (string.IsNullOrEmpty(originalContent))
                    {
                        Log.LogError("There is no data in the file [{0}]. Please check that this is the file you want to compress.", file);
                        continue;
                    }

                    if (actionType == ActionType.Css)
                    {
                        compressedContent = CssCompressor.Compress(originalContent,
                            0,
                            _cssCompressionType);
                    }
                    else if (actionType == ActionType.JavaScript)
                    {
                        JavaScriptCompressor jsCompressor = new JavaScriptCompressor(originalContent);
                        compressedContent =  jsCompressor.Compress(_lineBreak,
                            _loggingType == LoggingType.Detail,
                            _obfuscateJavaScript,
                            _preserveAllSemicolons,
                            _disableOptimizations);
                    }

                    compressedContentLength = compressedContent == null ? 0 : compressedContent.Length;

                    //Delete original file
                    bool fileDeleted = DeleteOriginalFile(file.ItemSpec, actionType);

                    //Save compressed file
                    SaveCompressedFile(file.ItemSpec, compressedContent, actionType, fileDeleted);

                    LogMessage(string.Format("Total original {0} file size: {1}. After compression: {2}. Compressed down to {3}% of original size.",
                        actionType.ToString(),
                        originalContentLength,
                        compressedContentLength,
                        100 - ((float)originalContentLength - (float)compressedContentLength) / (float)originalContentLength * 100));
                }
                catch (Exception exception)
                {
                    Log.LogError("Failed to read in the data for the path/file [{0}]. Please check the path/file name and make sure the file is not locked, read-only, etc..", file);
                    Log.LogErrorFromException(exception, false);
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool DeleteOriginalFile(string file, ActionType actionType)
        {
            bool toReturn = false;

            //Try and remove this file, if the user requests to do this.
            try
            {
                if ((actionType == ActionType.Css && _deleteCssFiles) || (actionType == ActionType.JavaScript && _deleteJavaScriptFiles))
                {
                    File.Delete(file);
                    LogMessage(string.Format("{0}Deleted original file [{1}]", System.Environment.NewLine, file), false);
                    toReturn = true;
                }
            }
            catch (Exception exception)
            {
                Log.LogError("Failed to delete the path/file [{0}]. Make sure that the file is not locked.", file);
                Log.LogErrorFromException(exception, false);
            }

            return toReturn;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void SaveCompressedFile(string originalFile, string compressedContent, ActionType actionType, bool fileDeleted)
        {
            string file = string.Format("{0}{1}{2}", 
                originalFile.Substring(0, originalFile.LastIndexOf('.')), 
                _newFileAppend, 
                originalFile.Substring(originalFile.LastIndexOf('.')));

            //Try and save compressed file
            try
            {
                File.WriteAllText(file, compressedContent == null ? string.Empty : compressedContent.ToString());
                Log.LogMessage("{0}Compressed content saved to file [{1}].", 
                    fileDeleted ? "" : System.Environment.NewLine, 
                    file);
            }
            catch (Exception exception)
            {
                Log.LogError("Failed to save the compressed text into the output file [{0}]. Please check the path/file name and make sure the file is not locked, read-only, etc..", file);
                Log.LogErrorFromException(exception, false);
            }
        }

        public override bool Execute()
        {
            DateTime startTime;

            //Check to make sure we have the bare minimum arguments supplied to the task.
            if (CssFiles.IsNullOrEmpty() && JavaScriptFiles.IsNullOrEmpty())
            {
                Log.LogError("At least one css or javascript file is required to be compressed / minified.");
                return false;
            }

            Log.LogMessage("Starting Css/Javascript compression...");
            startTime = DateTime.Now;

            if (!CssFiles.IsNullOrEmpty())
            {
                CompressFiles(ActionType.Css, CssFiles);
            }

            if (!JavaScriptFiles.IsNullOrEmpty())
            {
                CompressFiles(ActionType.JavaScript, JavaScriptFiles);
            }

            Log.LogMessage("{0}{0}Finished Css/Javascript compression.", System.Environment.NewLine);
            Log.LogMessage("Total time to execute task: {0}", (DateTime.Now - startTime).ToString());

            return true;
        }

        #endregion
    }
}